/* NOTICE
 
    Jim - the Java Image Manager, copyright (c) 2005 - 2007 Grant Gardner 
 
    Jim is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Jim is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  
    In addition, as a special exception, you are permitted to link this code
    with the 3rd party libraries required to compile this file and to
    distribute a linked combination including them. You must obey the
    GNU General Public License in all respects for all of the code except for
    those 3rd party libraries that are licenced under terms incompatible with the
    GNU General Public License.  
 
 
 NOTICE */

package au.com.lastweekend.jim.controller;

import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Image;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EventObject;
import java.util.Set;

import javax.swing.Action;
import javax.swing.JList;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.tree.DefaultTreeCellEditor;

import org.apache.log4j.Logger;
import org.flexdock.docking.DockingManager;
import org.flexdock.perspective.PerspectiveManager;
import org.flexdock.perspective.persist.FilePersistenceHandler;
import org.flexdock.perspective.persist.PersistenceHandler;

import au.com.lastweekend.desktop.Desktop;
import au.com.lastweekend.desktop.DesktopFactory;
import au.com.lastweekend.desktop.FileSelector;
import au.com.lastweekend.jim.actions.AdjustSlideCellSizeAction;
import au.com.lastweekend.jim.actions.DeleteSelectedFilesAction;
import au.com.lastweekend.jim.actions.FindLostFilesAction;
import au.com.lastweekend.jim.actions.ImageSearchAction;
import au.com.lastweekend.jim.actions.ImportSelectedImagesAction;
import au.com.lastweekend.jim.actions.InjectThumbnailAction;
import au.com.lastweekend.jim.actions.LaunchFolderChooserAction;
import au.com.lastweekend.jim.actions.LaunchFullScreenAction;
import au.com.lastweekend.jim.actions.MoveSelectedFilesAction;
import au.com.lastweekend.jim.actions.OpenFolderAction;
import au.com.lastweekend.jim.actions.ReloadActiveSheetAction;
import au.com.lastweekend.jim.actions.ResetPerspectiveAction;
import au.com.lastweekend.jim.actions.RotateAction;
import au.com.lastweekend.jim.actions.ToggleRotateByTagAction;
import au.com.lastweekend.jim.actions.AdjustSlideCellSizeAction.AdjustSlideCellSizeController;
import au.com.lastweekend.jim.actions.FindLostFilesAction.FindLostFilesController;
import au.com.lastweekend.jim.actions.MoveSelectedFilesAction.MoveSelectedFilesController;
import au.com.lastweekend.jim.actions.OpenFolderAction.OpenFolderController;
import au.com.lastweekend.jim.actions.ReloadActiveSheetAction.ReloadActiveSheetController;
import au.com.lastweekend.jim.actions.RotateAction.RotateActionController;
import au.com.lastweekend.jim.controller.FullScreenController.FullScreenParentController;
import au.com.lastweekend.jim.imagebase.ImageManager;
import au.com.lastweekend.jim.model.ContactSheetBaseModel;
import au.com.lastweekend.jim.model.ContactSheetImageItem;
import au.com.lastweekend.jim.model.ContactSheetListItem;
import au.com.lastweekend.jim.model.ContactSheetListModel;
import au.com.lastweekend.jim.model.ContactSheetSelectedItems;
import au.com.lastweekend.jim.model.ImageDirectoryModel;
import au.com.lastweekend.jim.model.JimConfiguration;
import au.com.lastweekend.jim.model.JimModel;
import au.com.lastweekend.jim.swing.PopupMenuMouseListener;
import au.com.lastweekend.jim.swing.SwingUtil;
import au.com.lastweekend.jim.swing.folderchooser.FolderChooser;
import au.com.lastweekend.jim.swing.folderchooser.FolderChooserController;
import au.com.lastweekend.jim.swing.folderchooser.ToolBarFolderChooser;
import au.com.lastweekend.jim.util.LoaderThread;
import au.com.lastweekend.jim.util.PreviewImageStruct;
import au.com.lastweekend.jim.view.ContactSheet;
import au.com.lastweekend.jim.view.JimFrame;
import au.com.lastweekend.jim.view.JimViewPort;
import au.com.lastweekend.jim.view.JimFullScreen.JimFullScreenModel;

/**
 * @author ggardner
 * 
 */
public class JimController
        implements
            FolderChooserController,
            SelectedItemsController,
            SelectedImagesController,
            OpenFolderController,
            AdjustSlideCellSizeController,
            MoveSelectedFilesController,
            ReloadActiveSheetController,
            RotateActionController,
            FileSelector,
            FindLostFilesController,
            ImageSearchController,
            FullScreenParentController {

    static final Logger LOG = Logger.getLogger(JimController.class);

    private final class ContactSheetSelectionListener implements ListSelectionListener {

        public void valueChanged(ListSelectionEvent event) {

            JList list = (JList) event.getSource();
            if (list.getValueIsAdjusting()) {
                // do nothing
                return;
            }

            showSelectedImage((ContactSheetListItem) list.getSelectedValue());

        }
    }

    public class JimWindowAdapter extends WindowAdapter {

        @Override
        public void windowActivated(WindowEvent e) {

            // If we get the focus back, close the full screen
            fullScreenController.closeFullScreen();
        }

        @Override
        public void windowClosing(WindowEvent e) {

            saveOpenSheets();

            try {
                DockingManager.storeLayoutModel();
            } catch (Exception e1) {
                LOG.warn("Unable to persist dockable layout", e1);
            }

            e.getWindow().setVisible(false);
            jimFrame.setExtendedState(Frame.NORMAL);
            jimModel.getConfiguration().setFrameDimension(jimFrame.getSize());
            LOG.debug("Saved size " + jimModel.getConfiguration().getFrameDimension());
            jimModel.getConfiguration().store();

            e.getWindow().dispose();

            jimModel.getWorker().finishWork();

        }

    }

    private JimModel jimModel;
    private JimFrame jimFrame;

    private Collection<Action> actions = new ArrayList<Action>();

    private ContactSheetDnDController dragDropController;
    private ContactSheetDrillDownController drillDownController;
    private KeywordMetadataSelectionController keywordSelectionController;
    private ListSelectionListener listSelectionListener;
    private LoaderThread<PreviewImageStruct, Image> loaderThread;
    private FullScreenController fullScreenController;
    private MouseListener popupListener;

    public File getCurrentDirectory() {

        ContactSheet selectedSheet = jimFrame.getViewPort().getActiveContactSheet();
        if (selectedSheet != null) {
            ContactSheetBaseModel model = selectedSheet.getModel();
            if (model instanceof ImageDirectoryModel) {
                return ((ImageDirectoryModel) model).getDirectory();
            }
        }

        return null;
    }

    public JimController() {

        jimModel = new JimModel();
        jimFrame = new JimFrame(jimModel);
        JimViewPort jimViewPort = jimFrame.getViewPort();

        new KeywordSearchController(jimViewPort.getSearchPanel(), this);

        JTree keywordMetadataTree = jimViewPort.getKeywordTree();
        // The cell editor is part view in that it renders the tree, part controller in that
        // it handles the mouse events to start editing etc...
        // TODO make the tree cell editor layout the textfield over the checkbox label
        // Note that the cell editor must be set before the mutableTreeContoller is initialised
        keywordMetadataTree.setCellEditor(new DefaultTreeCellEditor(keywordMetadataTree, null) {

            @Override
            protected boolean shouldStartEditingTimer(EventObject event) {

                // The click pause click 1200 second delay interferes with clicking the checkbox, skip it.
                return false;
            }

            @Override
            public void addCellEditorListener(CellEditorListener l) {

                LOG.info("Added cell editor listener " + l);
                super.addCellEditorListener(l);
            }
        });

        new KeywordMutableTreeController(keywordMetadataTree, jimModel.getImageBase(), this);
        keywordSelectionController = new KeywordMetadataSelectionController(keywordMetadataTree);

        dragDropController = new ContactSheetDnDController(this);
        drillDownController = new ContactSheetDrillDownController();
        fullScreenController = new FullScreenController(this);
        listSelectionListener = new ContactSheetSelectionListener();

        initalisePreviewLoader();
        initialiseActions();

        jimFrame.buildMenuBar(actions);
        jimFrame.buildToolBar(actions);
        popupListener = new PopupMenuMouseListener(SwingUtil.createPopupMenu(actions));

        WindowAdapter windowAdapter = new JimWindowAdapter();
        jimFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
        jimFrame.addWindowListener(windowAdapter);
        jimFrame.addWindowFocusListener(windowAdapter);

    }

    private void initalisePreviewLoader() {

        loaderThread = new LoaderThread<PreviewImageStruct, Image>("PreviewLoader", new ImagePanelLoader(jimFrame.getViewPort()
                .getImagePanel(), jimModel.getImageCache()));
        loaderThread.setDaemon(true);
        loaderThread.start();

    }

    private void initialiseActions() {

        actions.clear();

        actions.add(new LaunchFolderChooserAction(this));
        actions.add(new OpenFolderAction(this, true));
        actions.add(new OpenFolderAction(this, false));

        actions.add(new MoveSelectedFilesAction(this, true));
        actions.add(new FindLostFilesAction(this, true));

        actions.add(new MoveSelectedFilesAction(this, false));
        actions.add(new DeleteSelectedFilesAction(this));
        actions.add(new ImportSelectedImagesAction(this));
        actions.add(new ImageSearchAction(this));
        actions.add(new FindLostFilesAction(this, false));

        actions.addAll(createOpenWithActions());

        actions.add(new InjectThumbnailAction(this));
        actions.add(new RotateAction(this, RotateAction.Rotation.ANTI_CLOCKWISE));
        actions.add(new RotateAction(this, RotateAction.Rotation.CLOCKWISE));
        actions.add(new ToggleRotateByTagAction(jimModel.getConfiguration(), JimConfiguration.ROTATE_BY_ORIENTATION));

        actions.add(new AdjustSlideCellSizeAction(this, -JimModel.SLIDE_SIZE_INCREMENT));
        actions.add(new AdjustSlideCellSizeAction(this, JimModel.SLIDE_SIZE_INCREMENT));
        actions.add(new ReloadActiveSheetAction(this));
        actions.add(new ResetPerspectiveAction());

        actions.add(new LaunchFullScreenAction(fullScreenController));

    }

    public Collection<Action> createOpenWithActions() {

        Collection<Action> actions = getDesktop().getActions("image/jpeg", this);
        for (Action action : actions) {
            SwingUtil.scaleActionIcon(action, 16);
            action.putValue(SwingUtil.MENU, "File:Open with");
            action.putValue(SwingUtil.TOOLBAR, true);
            action.putValue(SwingUtil.POPUP, true);

        }

        return actions;
    }

    private Desktop getDesktop() {

        return DesktopFactory.getDesktop();
    }

    public void start(String[] args) {

        Dimension frameDimension = jimModel.getConfiguration().getFrameDimension();
        LOG.debug("Restoring to " + frameDimension);
        jimFrame.setPreferredSize(frameDimension);

        PerspectiveManager.getInstance().setCurrentPerspective(JimViewPort.JP1, true);
        PersistenceHandler persister = FilePersistenceHandler.createDefault(jimModel.getDockingPersistenceFile());
        PerspectiveManager.setPersistenceHandler(persister);

        // load any previously persisted layouts
        try {
            DockingManager.loadLayoutModel(true);
        } catch (Exception e) {
            LOG.warn("Unable to load persisted dockable layouts", e);
            PerspectiveManager.getInstance().reset();
        }

        restoreSavedSheets();

        Runnable runnable = new Runnable() {

            public void run() {

                jimFrame.showGUI();

            }
        };

        javax.swing.SwingUtilities.invokeLater(runnable);

    }

    private void saveOpenSheets() {

        Collection<ContactSheet> sheets = jimFrame.getViewPort().getContactSheets();
        Collection<ContactSheetListModel> models = new ArrayList<ContactSheetListModel>(sheets.size());
        for (ContactSheet contactSheet : sheets) {
            models.add(contactSheet.getModel());
        }
        jimModel.saveOpenSheets(models);
    }

    private void restoreSavedSheets() {

        Collection<ContactSheetListModel> models = jimModel.restoreOpenSheets();
        for (ContactSheetListModel contactSheetListModel : models) {
            openContactSheet(contactSheetListModel);
        }
    }

    public void search(boolean andMatch, Set<String> selectedKeywords) {

        openContactSheet(jimModel.getContactSheetModel(andMatch, selectedKeywords));

    }

    public void search(String searchString) {

        openContactSheet(jimModel.getContactSheetModel(searchString));

    }

    public void openFolder(File chosenDir) {

        openContactSheet(jimModel.getContactSheetModel(chosenDir));
    }

    private void openContactSheet(ContactSheetListModel model) {

        JimViewPort viewPort = jimFrame.getViewPort();

        ContactSheet newSheet = viewPort.openContactSheet(model);
        newSheet.setSlideCellSize(jimModel.getConfiguration().getSlideSize());

        newSheet.getViewList().addListSelectionListener(listSelectionListener);
        newSheet.addPopupListener(popupListener);
        keywordSelectionController.addTo(newSheet);
        dragDropController.addTo(newSheet);
        drillDownController.addTo(newSheet);

    }

    public FolderChooser getDialogFolderChooser() {

        return jimFrame.getDialogFolderChooser();
    }

    public ToolBarFolderChooser getToolBarFolderChooser() {

        return jimFrame.getToolBarFolderChooser();
    }

    public FolderChooser getFolderChooser(Action a) {

        return a.getValue(SwingUtil.TOOLBAR) == null ? getDialogFolderChooser() : getToolBarFolderChooser();

    }

    public void adjustSlideSize(int increment) {

        int slideSize = jimModel.adjustSlideSize(increment);

        Collection<ContactSheet> contactSheets = jimFrame.getViewPort().getContactSheets();
        for (ContactSheet contactSheet : contactSheets) {
            contactSheet.setSlideCellSize(slideSize);
        }

    }

    public void doWork(Runnable work) {

        jimModel.getWorker().addWork(work);

    }

    public ContactSheetSelectedItems getSelectedItems() {

        ContactSheet activeSheet = jimFrame.getViewPort().getActiveContactSheet();
        return activeSheet == null ? null : new ContactSheetSelectedItems(activeSheet.getModel(), activeSheet.getSelectedItems());

    }

    public ImageManager getImageManager() {

        return jimModel.getImageManager();
    }

    public ContactSheetListModel getActiveContactSheetModel() {

        ContactSheet activeSheet = jimFrame.getViewPort().getActiveContactSheet();
        return activeSheet == null ? null : activeSheet.getModel();
    }

    public boolean isRotateByTag() {

        return jimModel.getConfiguration().isRotateByOrientation();
    }

    public Collection<File> getSelectedFiles() {

        ContactSheetSelectedItems selectedItems = getSelectedItems();
        Collection<File> results = new ArrayList<File>();
        for (ContactSheetListItem contactSheetListItem : selectedItems) {

            File file = contactSheetListItem.getFile();
            if (file.isFile()) {
                results.add(file);
            }
        }
        return results;
    }

    public File getFirstFile() {

        Collection<File> selectedFiles = getSelectedFiles();
        if (selectedFiles.isEmpty()) {
            return null;
        }
        return selectedFiles.iterator().next();
    }

    public void selectAllItems() {

        jimFrame.getViewPort().getActiveContactSheet().selectAll();
    }

    public JimFullScreenModel getFullScreenModel() {

        return jimModel;
    }

    private void showSelectedImage(ContactSheetListItem selectedItem) {

        jimModel.getMetadataModel().setListItem(selectedItem);
        String previewTitle = "Preview - ";
        PreviewImageStruct pii = null;
        if (selectedItem != null && (selectedItem instanceof ContactSheetImageItem)) {
            pii = new PreviewImageStruct(selectedItem.getFile(), selectedItem.getRotationAngle());
            previewTitle = previewTitle + selectedItem.getFile().getAbsolutePath();
        }
        loaderThread.setSelectedObject(pii);
        jimFrame.getViewPort().setPreviewTitle(previewTitle);
    }

    // Called from thread work after image updates.
    public void updateImage(final ContactSheetImageItem imageItem) {

        if (imageItem != null) {
            ContactSheet activeSheet = jimFrame.getViewPort().getActiveContactSheet();
            if (activeSheet != null) {
                if (activeSheet.getSelectedItem() == (imageItem)) {
                    Runnable swingWork = new Runnable() {

                        public void run() {

                            showSelectedImage(imageItem);

                        }

                    };
                    SwingUtilities.invokeLater(swingWork);
                }
            }
        }

    }

    public void resetSelection() {

        jimFrame.getViewPort().reSelect();

    }
}
